/*
 * This computer program is the confidential information and proprietary trade
 * secret of Apptonelabs, Inc. Possessions and use of this program must
 * conform strictly to the license agreement between the user and
 * Apptonelabs, Inc., and receipt or possession does not convey any rights
 * to divulge, reproduce, or allow others to use this program without specific
 * written authorization of Apptonelabs, Inc.
 * 
 * Copyright (c) 2012-2015 Apptonelabs, Inc. All Rights Reserved.
 */
package com.apptonelabs.vnc.decoder;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.ColorModel;
import java.awt.image.DirectColorModel;
import java.awt.image.MemoryImageSource;
import java.io.DataOutput;
import java.io.IOException;

import com.apptonelabs.vnc.vphone.RfbInputStream;

//
// This is base decoder class.
// Other classes will be childs of RawDecoder.
//

public class RawDecoder {
	final static int EncodingRaw = 0;

	public RawDecoder(Graphics g, RfbInputStream is) {
		setGraphics(g);
		setRfbInputStream(is);
	}

	public RawDecoder(Graphics g, RfbInputStream is, int frameBufferW,
			int frameBufferH) {
		setGraphics(g);
		setRfbInputStream(is);
		setFrameBufferSize(frameBufferW, frameBufferH);
		// FIXME: cm24 created in getColorModel24.
		// Remove if no bugs
		cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
	}

	//
	// Set methods to set value of non-static protected members of class
	//

	public void setRfbInputStream(RfbInputStream is) {
		rfbis = is;
	}

	public void setGraphics(Graphics g) {
		graphics = g;
	}

	public void setBPP(int bpp) {
		bytesPerPixel = bpp;
	}

	public void setFrameBufferSize(int w, int h) {
		framebufferWidth = w;
		framebufferHeight = h;
	}

	//
	// FIXME: Rename this method after we don't need RecordInterface
	// in RawDecoder class to record session
	//

	public void setDataOutputStream(DataOutput os) {
		dos = os;
	}

	//
	// Decodes Raw Pixels data and draw it into graphics
	//

	public void handleRect(int x, int y, int w, int h) throws IOException,
			Exception {

		//
		// Write encoding ID to record output stream
		//

		if ((dos != null) && (enableEncodingRecordWritting)) {
			dos.writeInt(RawDecoder.EncodingRaw);
		}

		if (bytesPerPixel == 1) {
			for (int dy = y; dy < y + h; dy++) {
				if (pixels8 != null) {
					rfbis.readFully(pixels8, dy * framebufferWidth + x, w);
				}
				//
				// Save decoded data to record output stream
				//
				if (dos != null) {
					dos.write(pixels8, dy * framebufferWidth + x, w);
				}
			}
		} else {
			byte[] buf = new byte[w * 4];
			int i, offset;
			for (int dy = y; dy < y + h; dy++) {
				rfbis.readFully(buf);
				//
				// Save decoded data to record output stream
				//
				if (dos != null) {
					dos.write(buf);
				}
				offset = dy * framebufferWidth + x;
				if (pixels24 != null) {
					for (i = 0; i < w; i++) {
						pixels24[offset + i] = (buf[i * 4 + 2] & 0xFF) << 16
								| (buf[i * 4 + 1] & 0xFF) << 8
								| (buf[i * 4] & 0xFF);
					} // for
				} // if
			} // for
		} // else
		handleUpdatedPixels(x, y, w, h);
	} // void

	//
	// Display newly updated area of pixels.
	//

	protected void handleUpdatedPixels(int x, int y, int w, int h) {
		// Draw updated pixels of the off-screen image.
		pixelsSource.newPixels(x, y, w, h);
		graphics.setClip(x, y, w, h);
		graphics.drawImage(rawPixelsImage, 0, 0, null);
		graphics.setClip(0, 0, framebufferWidth, framebufferHeight);
	}

	//
	// Updates pixels data.
	// This method must be called when framebuffer is resized
	// or BPP is changed.
	//

	public void update() {
		// Images with raw pixels should be re-allocated on every change
		// of geometry or pixel format.
		int fbWidth = framebufferWidth;
		int fbHeight = framebufferHeight;

		if (bytesPerPixel == 1) {
			pixels24 = null;
			pixels8 = new byte[fbWidth * fbHeight];
			pixelsSource = new MemoryImageSource(fbWidth, fbHeight,
					getColorModel8(), pixels8, 0, fbWidth);
		} else {
			pixels8 = null;
			pixels24 = new int[fbWidth * fbHeight];
			pixelsSource = new MemoryImageSource(fbWidth, fbHeight, cm24,
					pixels24, 0, fbWidth);
		}
		pixelsSource.setAnimated(true);
		rawPixelsImage = Toolkit.getDefaultToolkit().createImage(pixelsSource);
	}

	//
	// Private static members access methods
	//

	protected ColorModel getColorModel8() {
		if (cm8 == null) {
			cm8 = cm8 = new DirectColorModel(8, 7, (7 << 3), (3 << 6));
		}
		return cm8;
	}

	protected ColorModel getColorModel24() {
		if (cm24 == null) {
			cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
		}
		return cm24;
	}

	protected Color[] getColor256() {
		if (color256 == null) {
			color256 = new Color[256];
			for (int i = 0; i < 256; i++)
				color256[i] = new Color(cm8.getRGB(i));
		}
		return color256;
	}

	//
	// This method will be used by HextileDecoder to disable
	// double writting encoding id to record stream.
	//
	// FIXME: Try to find better solution than this.
	//

	protected void enableEncodingRecordWritting(boolean enable) {
		enableEncodingRecordWritting = enable;
	}

	//
	// Unique data for every decoder (? maybe not ?)
	//

	protected int bytesPerPixel = 4;
	protected int framebufferWidth = 0;
	protected int framebufferHeight = 0;
	protected RfbInputStream rfbis = null;
	protected Graphics graphics = null;
	protected DataOutput dos = null;
	protected boolean enableEncodingRecordWritting = true;

	//
	// This data must be shared between decoders
	//

	protected static byte[] pixels8 = null;
	protected static int[] pixels24 = null;
	protected static MemoryImageSource pixelsSource = null;
	protected static Image rawPixelsImage = null;

	//
	// Access to this static members only though protected methods
	//

	private static ColorModel cm8 = null;
	private static ColorModel cm24 = null;
	private static Color[] color256 = null;
}
